﻿using System;
using Lypshare.Flow.Packets;
using System.Timers;
using Thread = System.Threading;
using Lypshare.Net;
using System.Collections.Generic;

namespace Lypshare.Flow.Types
{
	/// <summary>
	/// Go back n.
	/// </summary>
    public class GoBackN : FlowControl
    {
        // --------------------------------------------------------
        // Class Variables
        // --------------------------------------------------------


        /// <summary>
        /// The _buffer.
        /// </summary>
        private List<FlowPacket> _buffer
            = new List<FlowPacket>();


        /// <summary>
        /// The _send sequ.
        /// </summary>
        private long _sendSequ = 0;


        /// <summary>
        /// The _receive sequ.
        /// </summary>
        private long _receiveSequ = 1;


        /// <summary>
        /// The _lock.
        /// </summary>
        private Thread.Semaphore _lock = null;


        /// <summary>
        /// The _ack timeout.
        /// </summary>
        private Timer _ackTimeout = null;


        // --------------------------------------------------------
        // Class Properties
        // --------------------------------------------------------


        /// <summary>
        /// Gets a value indicating whether this instance is empty.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is empty; otherwise, <c>false</c>.
        /// </value>
        public override bool IsEmpty 
        {
            get { return _buffer.Count == 0; }
        
        } // Property: IsEmpty


        // --------------------------------------------------------
        // Class Functions
        // --------------------------------------------------------


        /// <summary>
        /// Initializes a new instance of the <see cref="Lypshare.Flow.Types.GoBackN"/> class.
        /// </summary>
        /// <param name='endPoint'>
        /// End point.
        /// </param>
        public GoBackN(FlowEndPoint endPoint)
            : base(endPoint)
        {
            _lock = new Thread.Semaphore(5, 5);

            // Initialize ack timeout
            _ackTimeout = new Timer(250);

            _ackTimeout.Elapsed 
                += OnAckTimeoutElapsed;

        } // Constructor


        /// <summary>
        /// Send the specified packet.
        /// </summary>
        /// <param name='packet'>
        /// Packet.
        /// </param>
        public override void Send(Packet packet)
        {
            if (_lock.WaitOne())
            {
                var sequ = Thread.Interlocked.Increment(
                    ref _sendSequ);

                var flowPacket = new FlowPacket(
                    EndPoint.Id, (uint)sequ);

                // Set the packet content
                flowPacket.Content = packet;

                lock (_buffer)
                {
                    // Start ack timer
                    if (IsEmpty)
                        _ackTimeout.Start();

                    // Add packet to buffer
                    _buffer.Add(flowPacket);

                    EndPoint.Connection
                        .Send(flowPacket);

                } // lock (_buffer)
            } // if (_lock.WaitOne())
        } // Send


        /// <summary>
        /// Accept the specified packet.
        /// </summary>
        /// <param name='packet'>
        /// If set to <c>true</c> packet.
        /// </param>
        public override bool Accept(FlowPacket packet)
        {
            if (packet.Sequ == _receiveSequ)
            {
                // Send ack packet to remote host
                EndPoint.Connection.Send(new AckPacket(
                    packet.Sequ));

                // Increment the receive 
                // sequence for next packet
                _receiveSequ++;

                return true;

            } // if (packet.Sequ == _receiveSequ)

            return false;

        } // Accept


        /// <summary>
        /// Acknowledge the specified ack.
        /// </summary>
        /// <param name='ack'>
        /// Ack.
        /// </param>
        public override Packet[] Acknowledge(AckPacket ack)
        {
            lock (_buffer)
            {
                // Remove all packets from buffer
                // which are lower or equal to 
                // the ack sequ

                // Save all deleted packets
                var packets = new List<Packet>();

                _buffer.RemoveAll((packet) =>
                {
                    if (packet.Sequ <= ack.Sequ)
                    {
                        // Ein Signal freigeben
                        _lock.Release();

                        // Save packet as delete
                        packets.Add(packet);

                        // Stop ack timeout
                        _ackTimeout.Stop();

                        return true;

                    } // if (entry.Sequ <= ackPacket.Sequ)

                    return false;
                });

                if (!IsEmpty)
                    _ackTimeout.Start();

                // Return deleted packets
                return packets.ToArray();
                
            } // lock (_buffer)
        } // Acknowledge


        /// <summary>
        /// Releases all resource used by the <see cref="Lypshare.Flow.Types.GoBackN"/> object.
        /// </summary>
        /// <remarks>
        /// Call <see cref="Dispose"/> when you are finished using the <see cref="Lypshare.Flow.Types.GoBackN"/>.
        /// The <see cref="Dispose"/> method leaves the <see cref="Lypshare.Flow.Types.GoBackN"/> in an unusable
        /// state. After calling <see cref="Dispose"/>, you must release all references to the
        /// <see cref="Lypshare.Flow.Types.GoBackN"/> so the garbage collector can reclaim the memory that the
        /// <see cref="Lypshare.Flow.Types.GoBackN"/> was occupying.
        /// </remarks>
        public override void Dispose()
        {
            // Stop timeout
            _ackTimeout.Stop();

        } // Dispose


        // --------------------------------------------------------
        // Class Handles
        // --------------------------------------------------------


        /// <summary>
        /// Raises the ack timeout elapsed event.
        /// </summary>
        /// <param name='sender'>
        /// Sender.
        /// </param>
        /// <param name='e'>
        /// E.
        /// </param>
        private void OnAckTimeoutElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            lock (_buffer)
            {
                _buffer.ForEach((packet) => EndPoint
                    .Connection.Send(packet));

            } // lock (_buffer)

            // Restart timer
            _ackTimeout.Start();

        } // OnAckTimeoutElapsed

    } // class GoBackN
} // namespace Lypshare.Flow.Types